In [1]:
import pandas as pd
import os, re
import numpy as np
import soundfile as sf
from IPython.display import clear_output
import pickle
import librosa
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.utils import to_categorical
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping
from tensorflow.keras.models import load_model
from tensorflow.keras import Model
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.metrics import det_curve, DetCurveDisplay
import plotly.express as px
import plotly
import matplotlib.pyplot as plt
import plotly.graph_objects as go
In [2]:
long_data_frame_without_postprocess = pd.read_csv("C:/Users/zbugo/Desktop/praktyki_zadania/19/data/long_data_frame.csv")
long_data_frame = pd.read_csv("C:/Users/zbugo/Desktop/praktyki_zadania/19/data/long_data_frame_postprocess.csv")
In [3]:
df_curve_list = []
colors = []
postprocessing_name = []
all_data = []
list_for_FRR_FAR = []

Dane bez postprocessingu.¶

In [4]:
genuine = long_data_frame_without_postprocess[long_data_frame_without_postprocess['genuine']]
impostor = long_data_frame_without_postprocess[~long_data_frame_without_postprocess['genuine']]

data = genuine, impostor
all_data.append(data)
In [5]:
FAR, FRR, thresholds = det_curve(y_true=long_data_frame_without_postprocess['genuine'], y_score=long_data_frame_without_postprocess['score'])
arg_of_best_threshold = np.argmin(np.abs(FAR - FRR))

df_curve = pd.DataFrame({
    'False Acceptance Rate': FAR[::10],
    'False Rejection Rate': FRR[::10]
}, index=thresholds[::10])
df_curve.index.name = "Thresholds"
df_curve.columns.name = "Rate"

df_curve_list.append(df_curve)
colors.append('blue')
postprocessing_name.append('no postprocess')
FAR_FRR = FAR, FRR
list_for_FRR_FAR.append(FAR_FRR)

Dane z LDA.¶

In [6]:
genuine = long_data_frame[long_data_frame['genuine']]
impostor = long_data_frame[~long_data_frame['genuine']]

data = genuine, impostor
all_data.append(data)
In [7]:
FAR, FRR, thresholds = det_curve(y_true=long_data_frame['genuine'], y_score=long_data_frame['score'])
arg_of_best_threshold = np.argmin(np.abs(FAR - FRR))
y = np.mean(a = np.array(FAR[arg_of_best_threshold], FRR[arg_of_best_threshold]))
x = thresholds[arg_of_best_threshold]

df_curve = pd.DataFrame({
    'False Acceptance Rate': FAR[::10],
    'False Rejection Rate': FRR[::10]
}, index=thresholds[::10])
df_curve.index.name = "Thresholds"
df_curve.columns.name = "Rate"

df_curve_list.append(df_curve)
colors.append('red')
postprocessing_name.append('lda')
FAR_FRR = FAR, FRR
list_for_FRR_FAR.append(FAR_FRR)

Wykresy.¶

In [ ]:
fig, axes = plt.subplots(1, 2, figsize=(18, 8))  

for i in range(0, len(all_data)):
    col = i 

    
    ax = axes[col]

    ax.hist(all_data[i][0]['score'], bins=128, alpha=0.5, label='genuine')
    ax.hist(all_data[i][1]['score'], bins=128, alpha=0.5, label='impostor')
    ax.set_xlim(-1, 1)
    ax.legend()
    ax.set_title(postprocessing_name[i])
    ax.grid()

plt.show()
No description has been provided for this image

Widzimy, że rozkłady przed i po postprocessingu różnią się. Po zastosowaniu LDA wartości score dla impostorów stały się znacznie bardziej prawoskośne, a kurtoza wzrosła (co stwierdzam na podstawie wizualnej analizy). Jest to główna różnica. Histogramy dla genuine są niewielkie; choć różnica jest zauważalna, trudno wyciągnąć jednoznaczne wnioski. Przejdźmy do kolejnych wykresów, które dostarczą więcej informacji na temat wyników działania modelu.

In [9]:
plotly.offline.init_notebook_mode()
# Lista kolorów dla różnych modeli

# Tworzenie pustego wykresu
fig_thresh = go.Figure()

# Iteracyjne dodawanie krzywych do wykresu
for i, df_curve in enumerate(df_curve_list):
    legend_group = postprocessing_name[i]  # Grupa legendy dla modelu

    # Dodanie linii dla FAR (z przypisaną grupą legendy)
    fig_thresh.add_trace(go.Scatter(
        x=df_curve.index, y=df_curve['False Acceptance Rate'], 
        mode='lines', 
        line=dict(color=colors[i]),
        name='FAR',
        legendgroup=legend_group,  # Przypisanie do grupy
        showlegend=False  # Ukrycie wpisu dla FAR w legendzie
    ))
    
    # Dodanie linii dla FRR (z przypisaną grupą legendy)
    fig_thresh.add_trace(go.Scatter(
        x=df_curve.index, y=df_curve['False Rejection Rate'], 
        mode='lines', 
        line=dict(color=colors[i]),  # Inny styl dla FRR (przerywana linia)
        name='FRR',
        legendgroup=legend_group,  # Przypisanie do grupy
        showlegend=False  # Ukrycie wpisu dla FRR w legendzie
    ))

    # Znalezienie najlepszego threshold (EER)
    arg_of_best_threshold = np.argmin(np.abs(df_curve['False Acceptance Rate'] - df_curve['False Rejection Rate']))
    x = df_curve.index[arg_of_best_threshold]
    y = np.mean([df_curve['False Acceptance Rate'].iloc[arg_of_best_threshold], df_curve['False Rejection Rate'].iloc[arg_of_best_threshold]])

    # Dodanie punktu dla EER (z przypisaną grupą legendy)
    fig_thresh.add_trace(go.Scatter(
        x=[x], y=[y], 
        mode='markers', 
        marker=dict(size=10, color=colors[i]),
        name='EER',
        legendgroup=legend_group,  # Przypisanie do grupy
        showlegend=False  # Ukrycie wpisu dla EER w legendzie
    ))

    # Dodanie tylko jednego wpisu do legendy dla całego modelu
    fig_thresh.add_trace(go.Scatter(
        x=[None], y=[None],  # Wpis do legendy bez dodawania nowych danych
        mode='lines',
        line=dict(color=colors[i]),
        name=postprocessing_name[i],  # Nazwa modelu w legendzie
        legendgroup=legend_group,  # Przypisanie do tej samej grupy
        showlegend=True  # Pokaż w legendzie
    ))

# Dostosowanie osi i tytułu
fig_thresh.update_layout(
    title="FRR, FAR EER for types of post-processing",
    xaxis_title="Thresholds",
    width=1200,
    height=600
)

# Wyświetlenie wykresu
fig_thresh.show()

Na wykresie można odczytać wartości FAR i FRR dla różnych wartości thresholdu, co pozwala znaleźć kompromis w postaci EER, czyli punktu przecięcia się krzywych FAR i FRR. Im mniejsza wartość EER, tym lepsza skuteczność modelu. Wartości EER są niewielkie i porównywalne z najlepszymi modelami, które udało mi się stworzyć za pomocą GMM. Wynik można jeszcze bardziej poprawić, ponieważ model UBM został stworzony na podstawie jedynie 20 epok. Porównując działanie modelu z postprocessingiem i bez niego, lepsze wyniki uzyskuje się dla danych bez postprocessingu.

In [10]:
plotly.offline.init_notebook_mode()

fig = go.Figure()

for i in range(0,len(list_for_FRR_FAR)):
    FAR, FRR = list_for_FRR_FAR[i]
    fig.add_trace(go.Scatter(x=FAR, y=FRR, name=postprocessing_name[i], mode='lines'))

fig.update_layout(
    title='Krzywa DET',
    xaxis_title='FAR',
    yaxis_title='FRR',
    width=1200,
    height=600
)

fig.update_xaxes(range=[0, 0.35])
fig.update_yaxes(range=[0, 0.55])


fig.show()

Wykres przedstawia krzywą DET, która pokazuje, jak zmienia się FRR w zależności od FAR. Można na nim także odczytać EER – wartość, przy której FAR i FRR są równe. Punkt, w którym FAR = FRR, jest jedyny i reprezentuje wartość EER. Wykres jest również przydatny w sytuacjach, gdy jeden rodzaj błędu jest bardziej akceptowalny od drugiego, np. gdy lepiej jest nie dopuścić właściciela konta bankowego do wypłaty pieniędzy niż pozwolić na dostęp złodziejowi. W takim przypadku możemy sprawdzić, jaka jest wartość FRR przy akceptowalnym dla nas poziomie FAR. Analizując wykres, możemy zauważyć, że powinniśmy wybrać model bez postprocessingu.

In [11]:
fig, ax = plt.subplots(figsize=(18, 8))  # Ustaw szerokość i wysokość w calach

# Tworzenie obiektu DetCurveDisplay i rysowanie wykresu
for i in range(0, len(list_for_FRR_FAR)):

    FAR, FRR = list_for_FRR_FAR[i]
    display = DetCurveDisplay(fpr=FAR, fnr=FRR)
    display.plot(ax=ax, color = colors[i], label = postprocessing_name[i])  # Przekazanie osi 'ax' do metody plot()

ax.set_title('Krzywa DET (Detection Error Tradeoff).')

# Wyświetlenie wykresu
plt.grid();
No description has been provided for this image

Jest to ten sam wykres krzywej DET co wyżej, ale z inną skalą osi, co sprawia, że jest bardziej czytelny kosztem braku interaktywności.

Podsumowanie.¶

Korzystając z ekstraktora embeddingów, byłem w stanie ocenić, jak model radzi sobie zarówno z danymi bez postprocessingu, jak i po jego zastosowaniu z użyciem LDA. Model lepiej działa na danych bez postprocessingu. Wyniki działania (wartość EER) są zadowalające, jednak można je jeszcze poprawić, trenując model przez więcej niż 20 epok.